#include "socket.h"
#include "iomanager.h"
#include "log.h"
#include "macro.h"
#include "hook.h"
#include <sstream>

// Done

Socket::Socket(int family, int type, int protocol)
    : m_socket(-1), m_family(family), m_type(type), m_protocol(protocol), m_isConnected(false)
{
}

Socket::~Socket() { close(); }

// 创建TCP Socket(满足地址类型)
Socket::ptr Socket::CreateTCP(Address::ptr address)
{
    Socket::ptr socket(new Socket(address->getFamily(), TCP, 0));
    return socket;
}

// 创建UDP Socket(满足地址类型)
Socket::ptr Socket::CreateUDP(Address::ptr address)
{
    Socket::ptr socket(new Socket(address->getFamily(), UDP, 0));
    socket->newSocket();
    socket->m_isConnected = true;
    return socket;
}

// 创建IPv4的TCP Socket
Socket::ptr Socket::CreateTCPSocket()
{
    Socket::ptr socket(new Socket(IPV4, TCP, 0));
    return socket;
}

// 创建IPv4的UDP Socket
Socket::ptr Socket::CreateUDPSocket()
{
    Socket::ptr socket(new Socket(IPV4, UDP, 0));
    socket->newSocket();
    socket->m_isConnected = true;
    return socket;
}

// 创建IPv6的TCP Socket
Socket::ptr Socket::CreateTCPSocket6()
{
    Socket::ptr socket(new Socket(IPV6, TCP, 0));
    return socket;
}

// 创建IPv6的UDP Socket
Socket::ptr Socket::CreateUDPSocket6()
{
    Socket::ptr socket(new Socket(IPV6, UDP, 0));
    socket->newSocket();
    socket->m_isConnected = true;
    return socket;
}

// 创建Unix的TCP Socket
Socket::ptr Socket::CreateUnixTCPSocket()
{
    Socket::ptr socket(new Socket(UNIX, TCP, 0));
    return socket;
}

// 创建Unix的UCP Socket
Socket::ptr Socket::CreateUnixUDPSocket()
{
    Socket::ptr socket(new Socket(UNIX, UDP, 0));
    return socket;
}

// 获取发送超时时间(毫秒)
uint64_t Socket::getSendTimeout()
{
    FdCtx::ptr ctx = FdMgr::GetInstance()->get(m_socket);
    if (ctx)
    {
        return ctx->getTimeout(SO_SNDTIMEO);
    }
    return -1;
}

// 设置发送超时时间(毫秒)
void Socket::setSendTimeout(uint64_t msec)
{
    struct timeval tv
    {
        int(msec / 1000),
            int(msec % 1000 * 1000)
    };
    setOption(SOL_SOCKET, SO_SNDTIMEO, tv);
}

// 获取接收超时时间(毫秒)
uint64_t Socket::getRecvTimeout()
{
    FdCtx::ptr ctx = FdMgr::GetInstance()->get(m_socket);
    if (ctx)
    {
        return ctx->getTimeout(SO_RCVTIMEO);
    }
}

// 设置接收超时时间(毫秒)
void Socket::setRecvTimeout(uint64_t msec)
{
    struct timeval tv
    {
        int(msec / 1000),
            int(msec % 1000 * 1000)
    };
    setOption(SOL_SOCKET, SO_RCVTIMEO, tv);
}

// 获取sockopt getsockopt
bool Socket::getOption(int level, int option, void *result, socklen_t *len)
{
    int ret = getsockopt(m_socket, level, option, result, (socklen_t *)len);
    if (ret)
    {
        LOG(DEBUG, "getOption socket= %d,level = %d option = %d, errno = %d errstr = %s", m_socket, level, option, errno, strerror(errno));
        return false;
    }
    return true;
}

bool Socket::setOption(int level, int option, const void *result, socklen_t len)
{
    if (setsockopt(m_socket, level, option, result, (socklen_t)len))
    {
        LOG(DEBUG, "setOption socket= %d,level = %d option = %d, errno = %d errstr = %s", m_socket, level, option, errno, strerror(errno));
        return false;
    }
    return true;
}

Socket::ptr Socket::accept()
{
    Socket::ptr sock(new Socket(m_family, m_type, m_protocol));
    int newsock = ::accept(m_socket, nullptr, nullptr);
    if (newsock == -1)
    {
        LOG(ERROR, "accept(%d) errno = %d errstr = %s", m_socket, errno, strerror(errno));
        return Socket::ptr();
    }

    if (sock->init(newsock))
        return sock;
    return Socket::ptr();
}

bool Socket::init(int sock)
{
    FdCtx::ptr ctx = FdMgr::GetInstance()->get(sock);
    if (ctx && ctx->isSocket() && !ctx->isClose())
    {
        m_socket = sock;
        m_isConnected = true;
        initSocket();
        getLocalAddress();
        getRemoteAddress();
        return true;
    }
    return false;
}

bool Socket::bind(const Address::ptr &addr)
{
    m_localAddress = addr;
    if (!isValid())
    {
        newSocket();
        if (SYLAR_UNLIKELY(!isValid()))
        {
            return false;
        }
    }

    if (SYLAR_UNLIKELY(addr->getFamily() != m_family))
    {
        LOG(ERROR, "bind socket.family( %d ) addr.family %d ) not equal,addr = %s", m_family, addr->getFamily(), addr->toString());
        return false;
    }

    UnixAddress::ptr uaddr = std::dynamic_pointer_cast<UnixAddress>(addr);
    if (uaddr)
    {
        Socket::ptr sock = Socket::CreateUnixTCPSocket();
        if (sock->connect(uaddr))
            return false;
        else
            FSUtil::Unlink(uaddr->getPath(), true);
    }

    if (::bind(m_socket, addr->getAddr(), addr->getAddrLen()))
    {
        LOG(ERROR, "bind error errno = %d errstr = %s", errno, strerror(errno));
        return false;
    }

    getLocalAddress();
    return true;
}

bool Socket::reconnect(uint64_t timeout_ms)
{
    if (!m_remoteAddress)
    {
        LOG(ERROR, "remote m_remoteAddress is null");
        return false;
    }
    m_localAddress.reset();
    return connect(m_remoteAddress, timeout_ms);
}

bool Socket::connect(const Address::ptr &addr, uint64_t timeout_ms)
{
    m_remoteAddress = addr;
    if (!isValid())
    {
        newSocket();
        if (SYLAR_UNLIKELY(!isValid()))
        {
            return false;
        }
    }

    if (SYLAR_UNLIKELY(addr->getFamily()) != m_family)
    {
        LOG(ERROR, "connect sock.family( %d ) not equal,addr = %s", addr->getFamily(), addr->toString());
        return false;
    }

    if (timeout_ms == (uint64_t)-1)
    {
        if (::connect(m_socket, addr->getAddr(), addr->getAddrLen()))
        {
            LOG(ERROR, "socket = %d connect( %s ) error errno = %d errstr = %s", m_socket, addr->toString(), errno, strerror(errno));
            close();
            return false;
        }
    }
    else
    {
        if (::connect_with_timeout(m_socket, addr->getAddr(), addr->getAddrLen(), timeout_ms))
        {
            LOG(ERROR, "sock = %d connect(%s) timeout = %d errno = %d errstr = %s", m_socket, addr->toString(), timeout_ms, errno, strerror(errno));
            close();
            return false;
        }
    }

    m_isConnected = true;
    getRemoteAddress();
    getLocalAddress();

    return true;
}

bool Socket::listen(int backlog)
{
    if (!isValid())
    {
        LOG(ERROR, "listen error socket = -1");
        return false;
    }
    if (::listen(m_socket, backlog))
    {
        LOG(ERROR, "listen errno = %d errstr = %s", errno, strerror(errno));
        return false;
    }
    return true;
}

bool Socket::close()
{
    if (!m_isConnected && m_socket)
        return true;
    m_isConnected = false;
    if (m_socket != -1)
    {
        ::close(m_socket);
        m_socket = -1;
    }
    return false;
}

int Socket::send(const void *buffer, size_t length, int flags)
{
    if (isConnected())
    {
        return ::send(m_socket, buffer, length, flags);
    }
    return -1;
}

int Socket::send(const iovec *buffers, size_t length, int flags)
{
    if (isConnected())
    {
        msghdr msg;
        memset(&msg, 0, sizeof(msg));
        msg.msg_iov = (iovec *)buffers;
        msg.msg_iovlen = length;
        return ::sendmsg(m_socket, &msg, flags);
    }
    return -1;
}

int Socket::sendTo(const void *buffer, size_t length, const Address::ptr to, int flags)
{
    if (isConnected())
    {
        return ::sendto(m_socket, buffer, length, flags, to->getAddr(), to->getAddrLen());
    }
    return -1;
}

int Socket::sendTo(const iovec *buffers, size_t length, const Address::ptr to, int flags)
{
    if (isConnected())
    {
        msghdr msg;
        memset(&msg, 0, sizeof(msg));
        msg.msg_iov = (iovec *)buffers;
        msg.msg_iovlen = length;
        msg.msg_name = to->getAddr();
        msg.msg_namelen = to->getAddrLen();
        return ::sendmsg(m_socket, &msg, flags);
    }
    return -1;
}

int Socket::recv(void *buffer, size_t length, int flags)
{
    if (isConnected())
    {
        return ::recv(m_socket, buffer, length, flags);
    }
    return -1;
}

int Socket::recv(iovec *buffers, size_t length, int flags)
{
    if (isConnected())
    {
        msghdr msg;
        memset(&msg, 0, sizeof(msg));
        msg.msg_iov = (iovec *)buffers;
        msg.msg_iovlen = length;
        return ::recvmsg(m_socket, &msg, flags);
    }
    return -1;
}

int Socket::recvFrom(void *buffer, size_t length, Address::ptr from, int flags)
{
    if (isConnected())
    {
        socklen_t len = from->getAddrLen();
        return ::recvfrom(m_socket, buffer, length, flags, from->getAddr(), &len);
    }
    return -1;
}

int Socket::recvFrom(iovec *buffers, size_t length, Address::ptr from, int flags)
{
    if (isConnected())
    {
        msghdr msg;
        memset(&msg, 0, sizeof(msg));
        msg.msg_iov = (iovec *)buffers;
        msg.msg_iovlen = length;
        msg.msg_name = from->getAddr();
        msg.msg_namelen = from->getAddrLen();
        return ::recvmsg(m_socket, &msg, flags);
    }
    return -1;
}

Address::ptr Socket::getRemoteAddress()
{
    if (m_remoteAddress)
    {
        return m_remoteAddress;
    }
    Address::ptr result;
    switch (m_family)
    {
    case AF_INET:
        result.reset(new IPv4Address());
        break;
    case AF_INET6:
        result.reset(new IPv6Address());
        break;
    case AF_UNIX:
        result.reset(new UnixAddress());
    default:
        result.reset(new UnknownAddress(m_family));
        break;
    }

    socklen_t addrlen = result->getAddrLen();
    if (getpeername(m_socket, result->getAddr(), &addrlen))
    {
        LOG(ERROR, "getpeername error socket = %d errno = %d errstr = %s", m_socket, errno, strerror(errno));
        return Address::ptr(new UnknownAddress(m_family));
    }

    if (m_family == AF_UNIX)
    {
        UnixAddress::ptr addr = std::dynamic_pointer_cast<UnixAddress>(result);
        addr->setAddrLen(addrlen);
    }

    m_remoteAddress = result;
    return m_remoteAddress;
}

Address::ptr Socket::getLocalAddress()
{
    if (m_localAddress)
        return m_localAddress;

    Address::ptr result;
    switch (m_family)
    {
    case AF_INET:
        result.reset(new IPv4Address());
        break;
    case AF_INET6:
        result.reset(new IPv6Address());
        break;
    case AF_UNIX:
        result.reset(new UnixAddress());
    default:
        result.reset(new UnknownAddress(m_family));
        break;
    }
    socklen_t addrlen = result->getAddrLen();
    if (getsockname(m_socket, result->getAddr(), &addrlen))
    {
        LOG(ERROR, "getpeername error socket = %d errno = %d errstr = %s", m_socket, errno, strerror(errno));
        return Address::ptr(new UnknownAddress(m_family));
    }

    if (m_family == AF_UNIX)
    {
        UnixAddress::ptr addr = std::dynamic_pointer_cast<UnixAddress>(result);
        addr->setAddrLen(addrlen);
    }

    m_localAddress = result;
    return m_localAddress;
}


int Socket::getError()
{
    int error = 0;
    socklen_t len = sizeof(error);
    if (!getOption(SOL_SOCKET, SO_ERROR, &error, &len))
    {
        error = errno;
    }
    return error;
}

std::ostream &Socket::dump(std::ostream &os) const
{
    os << "[Socket socket = " << m_socket
       << " is_connected = " << m_isConnected
       << " type = " << m_type
       << " protocol = " << m_protocol;
    if (m_localAddress)
    {
        os << " local_address = " << m_localAddress->toString();
    }
    if (m_remoteAddress)
    {
        os << " remote_address = " << m_remoteAddress->toString();
    }
    os << "]";
    return os;
}

std::string Socket::toString() const
{
    std::stringstream ss;
    dump(ss);
    return ss.str();
}

void Socket::initSocket()
{
    int val = 1;
    setOption(SOL_SOCKET, SO_REUSEADDR, val);
    if (m_type == SOCK_STREAM)
    {
        // 禁用Nagle算法以降低延迟
        setOption(IPPROTO_TCP, TCP_NODELAY, val);
    }
}

void Socket::newSocket()
{
    m_socket = socket(m_family, m_type, m_protocol);
    if (SYLAR_LIKELY(m_socket != -1))
    {
        initSocket();
    }
    else
    {
        LOG(ERROR, "socket( %d m_type = %d m_protocol = %d errno = %d errstr = %s", m_family, m_type, m_protocol, errno, strerror(errno));
    }
}

std::ostream &operator<<(std::ostream &os, const Socket &sock)
{
    return sock.dump(os);
}

// TODO
bool Socket::cancelRead()
{
    return IOManager::GetThis()->cancelEvent(m_socket, IOManager::READ);
}

bool Socket::cancelWrite()
{
    return IOManager::GetThis()->cancelEvent(m_socket, IOManager::WRITE);
}

bool Socket::cancelAll()
{
    return IOManager::GetThis()->cancelAll(m_socket);
}
